package org.infodavid.common.impl.services;

import java.util.Collection;
import java.util.List;
import java.util.Locale;

import javax.persistence.PersistenceException;

import org.apache.commons.lang.LocaleUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.impl.services.helpers.ServiceHelper;
import org.infodavid.common.model.IApplicationSettings;
import org.infodavid.common.model.IGenericProperty;
import org.infodavid.common.model.IOrganization;
import org.infodavid.common.model.criteria.IApplicationSettingsCriteria;
import org.infodavid.common.model.criteria.IGenericPropertyCriteria;
import org.infodavid.common.persistence.ApplicationSettingsDataService;
import org.infodavid.common.services.ApplicationSettingsService;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.exceptions.ServiceException;
import org.infodavid.common.util.PropertiesUtil;

/**
 * The Class ApplicationSettingsServiceImpl.
 */
@AImplementation(value = ApplicationSettingsService.class, runtime = ERuntime.DEFAULT)
public class ApplicationSettingsServiceImpl extends ApplicationSettingsService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(ApplicationSettingsServiceImpl.class);

  /** The helper. */
  private final ServiceHelper<IApplicationSettings,IApplicationSettings,Long,IApplicationSettingsCriteria> helper;

  /**
   * The Constructor.
   */
  protected ApplicationSettingsServiceImpl() {
    super();

    helper =
        new ServiceHelper<IApplicationSettings,IApplicationSettings,Long,IApplicationSettingsCriteria>(
            ApplicationSettingsDataService.getInstance());
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long add(final IApplicationContext context, final IApplicationSettings value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);
    final long key = helper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("Settings has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Settings has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findByProduct(org.infodavid.common.services
   * .IApplicationContext, java.lang.String)
   */
  @Override
  public IApplicationSettings findByProduct(final IApplicationContext context, final String value)
      throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IApplicationSettings.PRODUCT);
    }

    ServiceUtil.validate(context);

    IApplicationSettings result = context.getSettings();

    // we block the reload of settings
    if (result == null || !value.equals(result.getProduct())) {
      final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
      result =
          ((ApplicationSettingsDataService)helper.getDataService()).findByProduct(
              context.getPersistenceSession(), value);

      postExecute(evt.setResults(result));
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findOrganization(org.infodavid.common.
   * services.IApplicationContext)
   */
  @Override
  public IOrganization findOrganization(final IApplicationContext context)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);

    final IApplicationSettings settings = context.getSettings();
    IOrganization result;

    // we block the reload of settings
    if (settings == null || settings.getOrganization() == null) {
      final IInterceptorEvent evt = preExecute(context, FIND_ACTION);
      result =
          ((ApplicationSettingsDataService)helper.getDataService()).findOrganization(context
              .getPersistenceSession());

      postExecute(evt.setResults(result));
    }
    else {
      result = settings.getOrganization();
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public IApplicationSettings get(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    IApplicationSettings result = context.getSettings();;

    // we block the reload of settings
    if (result == null || result.getKey().longValue() != key) {
      final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
      result = helper.get(context, key);

      postExecute(evt.setResults(result));
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findEntities(final IApplicationContext context,
      final List<IApplicationSettings> entities, final IApplicationSettingsCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((ApplicationSettingsDataService)helper.getDataService()).find(
            context.getPersistenceSession(), entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public void remove(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, REMOVE_ACTION, key);
    final IApplicationSettings existing = helper.get(context, Long.valueOf(key));

    helper.remove(context, existing);
    postExecute(evt.setResults(existing));

    LOGGER.info("Settings has been removed (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Settings has been removed: " + existing + " (" + key + ')');
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#update(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void update(final IApplicationContext context, final IApplicationSettings value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, value);

    helper.update(context, value);
    postExecute(evt.setResults(value));

    LOGGER.info("Settings has been updated (" + value.getKey() + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Settings has been updated: " + value + " (" + value.getKey() + ')');
    }
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findProperty(org.infodavid.common.services
   * .IApplicationContext, java.lang.String, java.lang.String)
   */
  @Override
  public String findProperty(final IApplicationContext context, final String product,
      final String property) {
    final Object value = context.getProperties().get(property);
    String result = value instanceof String ? (String)value : null;

    if (result == null) {
      final IApplicationSettings settings = context.getSettings();

      if (settings != null) {
        result = settings.getProperties().getValue(property);

        if (result != null) {
          context.getProperties().put(property, result);
        }
      }
    }

    if (result == null) {
      final IApplicationSettings settings =
          ((ApplicationSettingsDataService)helper.getDataService()).findByProduct(
              context.getPersistenceSession(), product);

      if (settings != null) {
        result = settings.getProperties().getValue(property);

        if (result != null) {
          context.getProperties().put(property, result);
        }
      }
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findProperty(org.infodavid.common.services
   * .IApplicationContext, java.lang.String)
   */
  @Override
  public String findProperty(final IApplicationContext context, final String property) {
    final Object value = context.getProperties().get(property);
    String result = value instanceof String ? (String)value : null;

    if (result == null) {
      final IApplicationSettings settings = context.getSettings();

      if (settings != null) {
        result = settings.getProperties().getValue(property);

        if (result != null) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Found property in current application settings: " + result);
          }

          context.getProperties().put(property, result);
        }
      }
    }

    if (result == null) {
      result =
          ((ApplicationSettingsDataService)helper.getDataService()).findNotNullPropertyValue(
              context.getPersistenceSession(), property);

      if (result != null) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Property retrieved using stored application settings: " + result);
        }

        context.getProperties().put(property, result);
      }
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findBooleanProperty(org.infodavid.common
   * .services.IApplicationContext, java.lang.String, java.lang.String)
   */
  @SuppressWarnings("boxing")
  @Override
  public Boolean findBooleanProperty(final IApplicationContext context, final String product,
      final String property) throws PersistenceException, ServiceException {
    final String value = findProperty(context, product, property);

    return value == null ? null : PropertiesUtil.getInstance().toBoolean(value);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findBooleanProperty(org.infodavid.common
   * .services.IApplicationContext, java.lang.String)
   */
  @SuppressWarnings("boxing")
  @Override
  public Boolean findBooleanProperty(final IApplicationContext context, final String property)
      throws PersistenceException, ServiceException {
    final String value = findProperty(context, property);

    return value == null ? null : PropertiesUtil.getInstance().toBoolean(value);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findNumberProperty(org.infodavid.common
   * .services.IApplicationContext, java.lang.String, java.lang.String)
   */
  @Override
  public Number findNumberProperty(final IApplicationContext context, final String product,
      final String property) throws PersistenceException, ServiceException {
    final String value = findProperty(context, product, property);

    return value == null ? null : PropertiesUtil.getInstance().toNumber(value);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findNumberProperty(org.infodavid.common
   * .services.IApplicationContext, java.lang.String)
   */
  @Override
  public Number findNumberProperty(final IApplicationContext context, final String property)
      throws PersistenceException, ServiceException {
    final String value = findProperty(context, property);

    return value == null ? null : PropertiesUtil.getInstance().toNumber(value);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#getDefaultLocale(org.infodavid.common.
   * services.IApplicationContext)
   */
  @SuppressWarnings("rawtypes")
  @Override
  public Locale getDefaultLocale(final IApplicationContext context) throws PersistenceException,
      ServiceException {
    ServiceUtil.validate(context);
    final Object value = findProperty(context, DEFAULT_LOCALE_PROPERTY);
    Locale result = null;

    if (!(value instanceof String) || ((String)value).trim().length() == 0) {
      result = Locale.getDefault();
    }
    else {
      final List locales = LocaleUtils.countriesByLanguage((String)value);

      if (locales == null || locales.isEmpty()) {
        result = Locale.getDefault();
      }
      else {
        result = (Locale)locales.get(0);
      }
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#getDefaultTimeZone(org.infodavid.common
   * .services.IApplicationContext)
   */
  @Override
  public DateTimeZone getDefaultTimeZone(final IApplicationContext context)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);

    final Object value = findProperty(context, DEFAULT_TIME_ZONE_PROPERTY);
    DateTimeZone result = null;

    if (!(value instanceof String) || ((String)value).trim().length() == 0) {
      result = DateTimeZone.getDefault();
    }
    else {
      try {
        result = DateTimeZone.forOffsetHours(Double.valueOf((String)value).intValue());
      }
      catch (final Exception e) {
        LOGGER.warn("Cannot parse property value: " + DEFAULT_TIME_ZONE_PROPERTY + ",  value is "
            + value);

        result = DateTimeZone.getDefault();
      }
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#findProperties(org.infodavid.common.services
   * .IApplicationContext, java.util.Collection, java.lang.String,
   * org.infodavid.common.model.criteria.IGenericDataCriteria)
   */
  @Override
  public void findProperties(IApplicationContext context, Collection<IGenericProperty> entities,
      String product, IGenericPropertyCriteria criteria) throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    ServiceUtil.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);

    ((ApplicationSettingsDataService)helper.getDataService()).findProperties(
        context.getPersistenceSession(), entities, product, criteria);
    postExecute(evt.setResults(entities));
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.ApplicationSettingsService#updateProperties(org.infodavid.common.
   * services.IApplicationContext, java.lang.String, java.util.Collection)
   */
  @Override
  public DateTime updateProperties(IApplicationContext context, String product,
      Collection<IGenericProperty> properties) throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    ServiceUtil.validate(properties);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, properties);

    DateTime modificationDate =
        ((ApplicationSettingsDataService)helper.getDataService()).updateProperties(
            context.getPersistenceSession(), product, properties);
    postExecute(evt.setResults(properties));

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(properties.size() + " properties has been updated");
    }

    return modificationDate;
  }
}
